<?php
/**************************************************************************
 *
 *   Copyright 2010 American Public Media Group
 *
 *   This file is part of AIR2.
 *
 *   AIR2 is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   AIR2 is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with AIR2.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************/

require_once 'AIR2_CRUD_Controller.php';
require_once 'tank/CSVImporter.php';

/**
 * CSV Upload Controller
 *
 * Provides mechanisms for uploading and processing a CSV file.
 *
 * @author rcavis
 * @package default
 */
class Upload_Controller extends AIR2_Controller {

    /* csv file field name */
    public static $FIELD_NAME = 'csvfile';

    /* csv parsing options, and default vals */
    protected $csv_delim = ',';
    protected $csv_encl = '"';
    protected $csv_prev_size = 3;


    /**
     * Setup upload directory
     */
    public function begin() {
        parent::begin();
        $this->load->library('AirQauthz', array('authz_obj' => $this->airuser->get_authz()));

        // get any GET/POST arguments
        $this->_get_csv_args();
    }


    /**
     * Exposed method to list csv tank records:
     *   GET air2/upload/
     *   GET air2/upload/1234/
     *
     * @param string  $uuid
     */
    public function list_tanks($uuid=null) {
        $q = $this->_get_tank_query($uuid);

        // add query args and execute
        $this->airqargs->process_args($q, $this->query_args);
        $this->airqauthz->add_authz($q);
        $data = $this->airquery->execute_query($q, array(
                'return_list' => ($uuid === null),
                'whitelist' => array('tank_actm_id'),
            ));
        $this->response($data);
    }


    /**
     * Exposed method to list ActivityMaster records that are valid for
     * CSV-upload type tanks.
     *   GET air2/upload/activity
     *
     * @param string  $uuid
     */
    public function list_valid_actm() {
        $q = Doctrine_Query::create()->from('ActivityMaster');
        $q->where('actm_table_type = ?', ActivityMaster::$TABLE_TYPE_SOURCE);
        $q->where('actm_id < 15'); //TODO: this is completely arbitrary

        // add query args and execute
        $this->airqargs->process_args($q, $this->query_args);
        $this->airqauthz->add_authz($q);
        $data = $this->airquery->execute_query($q, array('whitelist' => array('actm_id')));
        $this->response($data);
    }


    /**
     * Exposed method to upload CSV files:
     *   POST air2/upload/
     *   PUT air2/upload/1234/
     *   POST air2/upload/1234/
     *
     * @param string  $uuid
     */
    public function do_upload($uuid=null) {
        $rec = $this->_get_tank_for_upload($uuid);
        $msg = $this->process_upload($rec);

        // re-fetch the tank data
        $q = $this->_get_tank_query($rec->tank_uuid);
        $this->airqauthz->add_authz($q);
        $data = $this->airquery->execute_query($q, array(
                'return_list' => false,
                'whitelist' => array('tank_actm_id'),
            ));
        $data['success'] = true;
        $data['message'] = $msg;
        $this->response($data);
    }


    /**
     * Exposed method to preview a CSV upload:
     *   GET air2/upload/1234/preview
     *
     * @param string  $uuid
     */
    public function get_preview($uuid) {
        $q = $this->_get_tank_query($uuid);
        $rec = $q->fetchOne();
        if (!$rec) $this->show_404();

        $rec->set_meta_field('csv_delim', $this->csv_delim);
        $rec->set_meta_field('csv_encl', $this->csv_encl);
        $imp = new CSVImporter($rec);
        $prev_data = $imp->preview_file($this->csv_prev_size);

        // validate headers
        $hdr_msg = $imp->validate_headers();
        $hdr_valid = ($hdr_msg === true) ? true : false;
        $rec->set_meta_field('valid_header', $hdr_valid);
        $rec->save();

        // count the csv file (max 1000)
        $count = $imp->get_line_count(1000);
        if ($count === false) $count = 'Greater than 1000 lines';

        // re-fetch the tank data
        $q = $this->_get_tank_query($rec->tank_uuid);
        $this->airqauthz->add_authz($q);
        $data = $this->airquery->execute_query($q, array(
                'return_list' => false,
                'whitelist' => array('tank_actm_id'),
            ));
        $data['success'] = $hdr_valid;
        $data['message'] = $hdr_msg;
        $data['size'] = $rec->get_meta_field('file_size');
        $data['lines'] = $count;
        $data['header'] = $prev_data['header'];
        $data['preview'] = $prev_data['lines'];
        $this->response($data);
    }


    /**
     * Exposed method to submit a csv upload for importing:
     *   POST air2/upload/1234/submit
     *
     * @param string  $uuid
     */
    public function do_submit($uuid) {
        $q = $this->_get_tank_query($uuid);
        $this->airqauthz->add_authz($q);
        $rec = $q->fetchOne();
        if (!$rec) $this->show_404();

        // check that tank_status is (N)ew or (F)ailed
        $valid = array(Tank::$STATUS_NEW, Tank::$STATUS_IMPORT_FAILED);
        if (!in_array($rec->tank_status, $valid)) {
            $this->_error_msg('Unable to submit CSV at this time');
        }

        // update/set the activity info
        if ($this->input->post('org_uuid')) {
            $org = Doctrine::getTable('Organization')->findOneBy('org_uuid', $this->input->post('org_uuid'));
            if (!$org) $this->_error_msg('Invalid Organization specified!');
            $rec->Organization = $org;
        }
        if ($this->input->post('prj_uuid')) {
            $prj = Doctrine::getTable('Project')->findOneBy('prj_uuid', $this->input->post('prj_uuid'));
            if (!$prj) $this->_error_msg('Invalid Project specified!');
            $rec->Project = $prj;
        }
        if ($this->input->post('tank_actm_id')) {
            $actm = Doctrine::getTable('ActivityMaster')->find($this->input->post('tank_actm_id'));
            if (!$actm) $this->_error_msg('Invalid Activity type specified!');
            $rec->ActivityMaster = $actm;
        }
        if ($this->input->post('tank_notes')) {
            $rec->tank_notes = $this->input->post('tank_notes');
        }
        if ($this->input->post('tank_dtim')) {
            $rec->tank_dtim = $this->input->post('tank_dtim');
        }
        $rec->set_meta_field('csv_delim', $this->csv_delim);
        $rec->set_meta_field('csv_encl', $this->csv_encl);
        $rec->set_meta_field('submit_message', null);
        $rec->set_meta_field('submit_success', null);
        $rec->save();

        // check column headers
        $imp = new CSVImporter($rec);
        $valid = $imp->validate_headers();
        if ($valid !== true) {
            $this->_error_msg($valid);
        }

        // import in foreground... TODO: param for bg processing
        $n = $imp->import_file();
        if (is_string($n)) {
            $success = false;
            $msg = $n;
        }
        else {
            $success = true;
            $msg = "Successfully imported $n rows";

            // add to job_queue
            if (!$this->input->get_post('nojob')) {
                $this->queue_discriminator($rec->tank_id);
            }
        }
        $rec->set_meta_field('submit_message', $msg);
        $rec->set_meta_field('submit_success', $success);
        $rec->save();

        // re-fetch the tank data
        $data = $this->airquery->execute_query($q, array(
                'return_list' => false,
                'whitelist' => array('tank_actm_id'),
            ));
        $data['success'] = $success;
        $data['message'] = $msg;
        $this->response($data);
    }


    /**
     * Process an uploaded file for a tank record, validating it and putting it
     * into the correct directory.
     *
     * @param Tank    $rec
     * @return string success message
     */
    protected function process_upload(&$rec) {
        // check for file existence
        if (!isset($_FILES[self::$FIELD_NAME])) {
            $this->_error_msg('No file uploaded to field: '.self::$FIELD_NAME);
        }
        if ($_FILES[self::$FIELD_NAME]['error'] != UPLOAD_ERR_OK) {
            $this->_error_msg('Server error uploading file');
        }

        $upl_name = $_FILES[self::$FIELD_NAME]['name'];
        $upl_size = $_FILES[self::$FIELD_NAME]['size'];
        $upl_path = $_FILES[self::$FIELD_NAME]['tmp_name'];

        // manually check file extension... CI doesn't seem to catch this
        if (!preg_match('/\.csv$/', $upl_name)) {
            $this->_error_msg("Uploaded file must have the extension '.csv'");
        }

        // check for same filename as existing
        if ($rec->exists() && $upl_name != $rec->tank_file_name) {
            $this->_error_msg("Error: you must upload the original file '".
                "$upl_name', or start a new csv import.");
        }
        $rec->tank_file_name = $upl_name;
        $rec->copy_file($upl_path);

        // save filesize in metadata
        $size_kb = $upl_size / 1024;
        $rec->set_meta_field('file_size', "$size_kb KB");
        $rec->tank_upd_dtim = air2_date(); // uploading counts as update

        // clear submit errors (if any) and set valid file
        $rec->set_meta_field('submit_message', null);
        $rec->set_meta_field('submit_success', null);
        $rec->set_meta_field('valid_header', null);
        $rec->set_meta_field('valid_file', true);
        $rec->save();

        return "Uploaded $upl_name ($size_kb KB)";
    }


    /**
     * Put an entry in the JobQueue to run the discriminator on this tank_id.
     *
     * @param int     $tank_id
     */
    protected function queue_discriminator($tank_id) {
        $proc = realpath(APPPATH.'../bin/run-discriminator');
        $jq = new JobQueue();
        $jq->jq_job = "$proc $tank_id";
        $jq->save();
    }


    /**
     * Helper function to return an error message
     *
     * @param string  $str
     */
    private function _error_msg($str) {
        $this->response(array(
                'success' => false,
                'message' => $str,
            ));
    }


    /**
     * Helper function to build a query to get tank records
     *
     * @param string  $uuid
     * @return Doctrine_Query
     */
    private function _get_tank_query($uuid=null) {
        $q = Doctrine_Query::create()->from('Tank a');
        $q->addWhere('a.tank_type = ?', Tank::$TYPE_CSV);
        $q->leftJoin('a.Organization o');
        $q->leftJoin('a.Project p');
        $q->leftJoin('a.User u');
        $q->leftJoin('a.ActivityMaster m');
        if ($uuid) $q->addWhere('a.tank_uuid = ?', $uuid);

        // select editable org_uuid/prj_uuid at base level
        $q->addSelect('o.org_uuid as org_uuid');
        $q->addSelect('p.prj_uuid as prj_uuid');
        $q->addSelect('u.user_uuid as user_uuid');
        return $q;
    }


    /**
     * Helper function to get a tank record for uploading a file to.
     *
     * @param string  $uuid
     * @return Tank
     */
    private function _get_tank_for_upload($uuid=null) {
        if ($uuid) {
            $q = $this->_get_tank_query($uuid);
            $t = $q->fetchOne();
            if (!$t) {
                $this->show_404();
            }
            elseif (!in_array($t->tank_status, array(Tank::$STATUS_NEW, Tank::$STATUS_IMPORT_FAILED))) {
                $this->show_403();
            }
            return $t;
        }
        else {
            $t = new Tank();
            $t->tank_uuid = air2_generate_uuid();
            $t->tank_user_id = AIR2_REMOTE_USER_ID;
            $t->tank_type = Tank::$TYPE_CSV;
            $t->tank_status = Tank::$STATUS_NEW;
            return $t;
        }
    }


    /**
     * Helper function to parse CSV parameters from GET or POST
     */
    private function _get_csv_args() {
        if ($this->request_method == 'POST') {
            if ($this->input->post('d')) $this->csv_delim = $this->input->post('d');
            if ($this->input->post('e')) $this->csv_encl = $this->input->post('e');
            if ($this->input->post('z')) $this->csv_prev_size = $this->input->post('z');
        }
        else {
            if ($this->input->get('d')) $this->csv_delim = $this->input->get('d');
            if ($this->input->get('e')) $this->csv_encl = $this->input->get('e');
            if ($this->input->get('z')) $this->csv_prev_size = $this->input->get('z');
        }
    }


}
